#pragma once

#include "Uses.h"
#include "Utilities.h"

Uses::Uses(void){
	usesLVarTable = LVarTableType();
	usesLineTable = LineTableType();
	usesPVarTable = PVarTableType();
	usesProcTable = ProcTableType();
	usesLineBitMap = Type::Map::Bit();
	usesProcBitMap = Type::Map::Bit();
}

Uses::~Uses(void){
	clear();
}

void Uses::clear(){
	usesLVarTable.clear();
	usesLineTable.clear();
	usesPVarTable.clear();
	usesProcTable.clear();
	usesLineBitMap.clear();
	usesProcBitMap.clear();
}

// set uses relationship for the given line and variable, returns status of operation
bool Uses::setLineUses(Type::Index::Line line, Type::Entity::Line entity, Type::Index::Var var){
	// invalid indices or entity
	if (!Utilities::isValidLineIndex(line) || !Utilities::isValidVarIndex(var) 
		|| !Utilities::isValidLineEntity(entity)){
			return false;
	}

	Type::List::Var varList;
	Type::Map::EntityLine entityLineMap;
	Type::List::Line allLineList;
	Type::List::Line lineList;
	bool isNonExisted = true;

	/********************** usesLVarTable*******************************/
	// load existing var list of the line
	if(isLineExistsInVar(line)){
		varList = usesLVarTable.at(line);
		usesLVarTable.erase(line);
	}

	if(!Utilities::isExistInList(varList,var)){
		// append new variable
		varList.push_back(var);
	}else{
		isNonExisted = false;
	}
	usesLVarTable.insert(std::make_pair<Type::Index::Line, Type::List::Var>(line, varList));

	/********************** usesLineTable*******************************/
	// load existing line list of the var 
	if(isVarExistsInLine(var)){
		entityLineMap = usesLineTable.at(var);
		usesLineTable.erase(var);

		// find ALL entity in EntityLineMap
		if(!entityLineMap.empty() && entityLineMap.find(allLineType) != entityLineMap.end()){
			allLineList = entityLineMap.at(allLineType);
			entityLineMap.erase(allLineType);
		}

		// find inserted entity in EntityLineMap
		if(!entityLineMap.empty() && entityLineMap.find(entity) != entityLineMap.end()){
			lineList = entityLineMap.at(entity);
			entityLineMap.erase(entity);
		}
	}

	if(!Utilities::isExistInList(allLineList,line)){
		// append new line list
		allLineList.push_back(line);
		lineList.push_back(line);
	}
	entityLineMap.insert(std::make_pair<Type::Entity::Line, Type::List::Line>(allLineType, allLineList));	
	entityLineMap.insert(std::make_pair<Type::Entity::Line, Type::List::Line>(entity, lineList));
	usesLineTable.insert(std::make_pair<Type::Constant, Type::Map::EntityLine>(var, entityLineMap));

	return isNonExisted;
}

bool Uses::setProcUses(Type::Index::Proc proc, Type::Index::Var var){
	// invalid indexes
	if (!Utilities::isValidProcIndex(proc) || !Utilities::isValidVarIndex(var)){
		return false;
	}

	Type::List::Var varList;
	Type::List::Proc procList;
	bool isNonExisted = true;

	/********************** usesPVarTable*******************************/
	// load existing var list of the proc
	if(isProcExistsInVar(proc)){
		varList = usesPVarTable.at(proc);
		usesPVarTable.erase(proc);
	}

	if(!Utilities::isExistInList(varList,var)){
		// append new variable
		varList.push_back(var);
	}else{
		isNonExisted = false;
	}
	usesPVarTable.insert(std::make_pair<Type::Index::Proc, Type::List::Var>(proc, varList));

	/********************** usesProcTable*******************************/
	// load existing proc list of the var
	if(isVarExistsInProc(var)){
		procList = usesProcTable.at(var);
		usesProcTable.erase(var);
	}

	if(!Utilities::isExistInList(procList,proc)){
		// append new proc
		procList.push_back(proc);
	}else{
		isNonExisted = false;
	}
	usesProcTable.insert(std::make_pair<Type::Index::Var, Type::List::Proc>(var, procList));

	return isNonExisted;
}

// set the bitmap (square matrix) for line uses relationship, 
// returns status of operation
bool Uses::setLineUsesBitMap(Type::Map::Bit& bitMap){
	if(!Utilities::isValidBitMap(bitMap)){
		return false;
	}

	Type::List::Bit varList = bitMap.front();
	Type::Map::Bit lineMap;
	int row = bitMap.size();
	int col = varList.size();
	int i;

	for(i=0; i<row;i++){
		varList = bitMap.at(i);
		lineMap.push_back(varList);
	}
	usesLineBitMap = lineMap;

	return true;
}

// set the bitmap (square matrix) for proc uses relationship, 
// returns status of operation
bool Uses::setProcUsesBitMap(Type::Map::Bit& bitMap){
	if(!Utilities::isValidBitMap(bitMap)){
		return false;
	}

	Type::List::Bit varList = bitMap.front();
	Type::Map::Bit procMap;
	int row = bitMap.size();
	int col = varList.size();
	int i;

	for(i=0; i<row;i++){
		varList = bitMap.at(i);
		procMap.push_back(varList);
	}
	usesProcBitMap = procMap;

	return true;
}

// get all used variables
const Type::List::Var& Uses::getUsedVars(){
	Type::List::Var* varList = new Type::List::Var();

	if(usesLineTable.size() > 0){
		for(LineTableType::iterator it=usesLineTable.begin(); it!=usesLineTable.end(); it++){
			varList->push_back(it->first);			
		}
	}

	return *varList;
}

// get the list of variables used by the given line
const Type::List::Var& Uses::getUsedVarsByLine(Type::Index::Line line){
	if(Utilities::isValidLineIndex(line) && isLineExistsInVar(line)){
		return usesLVarTable.at(line);
	}

	return *(new Type::List::Var());
}

// get the list of variables used by the given proc
const Type::List::Var& Uses::getUsedVarsByProc(Type::Index::Proc proc){
	if(Utilities::isValidProcIndex(proc) && isProcExistsInVar(proc)){
		return usesPVarTable.at(proc);
	}

	return *(new Type::List::Var());
}

// get the list of lines that uses the given variable
const Type::List::Line& Uses::getLinesUse(Type::Index::Var var){
	if(Utilities::isValidVarIndex(var) && isVarExistsInLine(var)){
		return usesLineTable.at(var).at(allLineType);
	}

	return *(new Type::List::Line());
}

// get the list of lines with given entity that uses the given variable
const Type::List::Line& Uses::getLinesUse(Type::Index::Var var, Type::Entity::Line entity){
	if(Utilities::isValidVarIndex(var) && Utilities::isValidLineEntity(entity) 
		&& isVarExistsInLine(var)){

		Type::Map::EntityLine* entityLineMap = &usesLineTable.at(var);
		if(entityLineMap->find(entity) != entityLineMap->end()){
			return entityLineMap->at(entity);
		}
	}

	return *(new Type::List::Line());
}

// get the list of procs that uses the given variable
const Type::List::Proc& Uses::getProcsUse(Type::Index::Var var){
	if(Utilities::isValidVarIndex(var) && isVarExistsInProc(var)){
		return usesProcTable.at(var);
	}

	return *(new Type::List::Proc());
}

// check if the given line uses the given variable 
bool Uses::isLineUses(Type::Index::Line line, Type::Index::Var var){
	if(!Utilities::isValidLineIndex(line) || !Utilities::isValidVarIndex(var)){
		return false;
	}

	if(!usesLineBitMap.empty() && (unsigned) line < usesLineBitMap.size()){
		Type::List::Bit varBitList = usesLineBitMap.at(line);
		if((unsigned) var < varBitList.size()){
			return varBitList.at(var);
		}
	}else if(isLineExistsInVar(line)){
		Type::List::Var varList = usesLVarTable.at(line);
		return Utilities::isExistInList(varList, var);
	}

	return false;
}

// check if the given proc uses the given variable 
bool Uses::isProcUses(Type::Index::Proc proc, Type::Index::Var var){
	if(!Utilities::isValidProcIndex(proc) || !Utilities::isValidVarIndex(var)){
		return false;
	}

	if(!usesProcBitMap.empty() && (unsigned) proc < usesProcBitMap.size()){
		Type::List::Bit varBitList = usesProcBitMap.at(proc);
		if((unsigned) var < varBitList.size()){
			return varBitList.at(var);
		}
	}else if(isProcExistsInVar(proc)){
		Type::List::Var varList = usesPVarTable.at(proc);
		return Utilities::isExistInList(varList, var);
	}

	return false;
}

// check if the given variable exists in line
bool Uses::isVarExistsInLine(Type::Index::Var var){
	return usesLineTable.find(var) != usesLineTable.end();
}

// check if the given line exists in var
bool Uses::isLineExistsInVar(Type::Index::Line line){
	return usesLVarTable.find(line) != usesLVarTable.end();
}


// check if the given variable exists in proc
bool Uses::isVarExistsInProc(Type::Index::Var var){
	return usesProcTable.find(var) != usesProcTable.end();
}

// check if the given proc exists in var
bool Uses::isProcExistsInVar(Type::Index::Proc proc){
	return usesPVarTable.find(proc) != usesPVarTable.end();
}